import java.io.File;
import java.io.FileFilter;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parse .*.o.cmd file, and extract MakeParam
 * 
 * @author dongbowen
 *
 */
public class CmdFileParser {

    /**
     * used to parse define
     * 
     * for example:
     * -D__KERNEL__
     * -D"KBUILD_STR(s)=\#s"
     * -D"I say:\"I am immortal\""
     * -DINITRAMFS_IMAGE="usr/initramfs_data.cpio"
     * 
     * group(1) is the value of define
     * 
     */
    private static final Pattern DEFINE_PATTERN = Pattern.compile("-D([^\"\\s]*\"(?:(?<=\\\\)\"|[^\"])+\"|\\S+)");
    
    /**
     * used to parse include directory
     * 
     * for example:
     * -I./arch/mips/include
     * 
     * group(1) is the directory
     * 
     */
    private static final Pattern INC_DIR_PATTERN = Pattern.compile("-I(\\S+)");
    
    /**
     * used to parse include file
     * 
     * for example:
     * -include ./include/linux/kconfig.h
     * 
     * group(1) is the include file
     * 
     */
    private static final Pattern INC_FILE_PATTERN = Pattern.compile("-include\\s+(\\S+)");
    
    /**
     * used to parse dependent header file
     * 
     * for example:
     * include/linux/slab.h
     * arch/mips/include/asm/types.h
     * 
     * exclude:
     * $(wildcard include/config/arch/supports/optimized/inlining.h)
     * 
     * group(1) is the header file
     */
    private static final Pattern DEP_HEADER_PATTERN = Pattern.compile("(\\S+\\.h)\\s+\\\\");
    
    /**
     * used to parse compile command
     * 
     * for example(ignore some parameter):
     * cmd_drivers/usb/core/port.o := mips-openwrt-linux-uclibc-gcc 
     *  -D"KBUILD_BASENAME=KBUILD_STR(port)"  -D"KBUILD_MODNAME=KBUILD_STR(usbcore)" 
     *  -c -o drivers/usb/core/port.o drivers/usb/core/port.c
     *  
     *  group(1) is the compiler
     *  group(2) is the source file
     */
    private static final Pattern COMPILE_CMD_PATTERN = 
            Pattern.compile("^cmd_\\S+\\.o\\s+:=\\s+((?:\\S+-)?gcc).*\\s(\\S+\\.[cS])");
    
    /**
     * parse kernel cmd file
     * 
     * @param cmdFile
     * @return
     */
    public static MakeParam parse(File cmdFile) {
        String cmd = FileUtils.readFile(cmdFile);
        if(cmd == null) {
            System.err.println("Fail to read " + cmdFile.getAbsolutePath());
            return null;
        }
        
        MakeParam makeParam = new MakeParam();
        Matcher matcher = null;
        
        String compileCommand = null;
        String others = null;
        
        try {
            
            /* parse compile command */
            matcher = COMPILE_CMD_PATTERN.matcher(cmd);
            if(matcher.find()) {
                makeParam.setCompiler(matcher.group(1));
                makeParam.addSrcFile(matcher.group(2));
                
                compileCommand = matcher.group();
                others = cmd.substring(cmd.indexOf(compileCommand) + compileCommand.length());
            } else {
                throw new Exception("Fail to parse compile command");
            }
            
            /* parse include directory */
            matcher = INC_DIR_PATTERN.matcher(compileCommand);
            while(matcher.find()) {
                makeParam.addIncludeDir(matcher.group(1));
            }
            
            /* parse include file */
            matcher = INC_FILE_PATTERN.matcher(compileCommand);
            while(matcher.find()) {
                makeParam.addIncludeFile(matcher.group(1));
            }
            
            /* parse define */
            matcher = DEFINE_PATTERN.matcher(compileCommand);
            while(matcher.find()) {
                makeParam.addDefine(matcher.group(1));
            }
            
            /* parse dependent header file */
            matcher = DEP_HEADER_PATTERN.matcher(others);
            while(matcher.find()) {
                makeParam.addDependHeaderFiles(matcher.group(1));
            }
            
        } catch (Exception e) {
            System.err.println("Fail to parse " + cmdFile.getAbsolutePath() + ", detail: " + e.getMessage());
            return null;
        }
        
        return makeParam;
    }
    
    /**
     * parse kernel cmd files
     * 
     * @param kernelRoot root directory of kernel
     */
    public static MakeParam parseKernel(File kernelRoot) {
        
        List<File> cmdFileList = FileUtils.getFile(kernelRoot, new FileFilter() {
            
            private Pattern cmdPattern = Pattern.compile(".*(?<!\\.mod|\\.built-in)\\.o\\.cmd");
            
            @Override
            public boolean accept(File file) {
                if(file.isDirectory()) {
                    return true;
                }
                
                return cmdPattern.matcher(file.getName()).find();
            }
        });
        
        MakeParam totalMakeParam = new MakeParam();
        
        for(File file : cmdFileList) {
            System.out.println(file.getAbsolutePath());
            MakeParam makeParam = parse(file);
            if(makeParam != null) {
                totalMakeParam.merge(makeParam);
            }
        }
        
        
        return totalMakeParam;
    }
    
    public static void filter(MakeParam makeParam) {
        makeParam.filter(new SimpleMakeParamFilter());
        makeParam.addDefine("KBUILD_BASENAME=KBUILD_STR(basename)");
        makeParam.addDefine("KBUILD_MODNAME=KBUILD_STR(modname)");
        // fix the fucking problem that eclipse doesn't recognize __label__
        makeParam.addDefine("__label__=int");
    }
    
    public static void main(String[] args) {
        
        String kernelPath = "/home/dongbowen/work/openwrt/openwrt/build_dir/target-mips_34kc_uClibc-0.9.33.2/"
                + "linux-ar71xx_generic/linux-3.18.45/usr";
        MakeParam makeParam = parseKernel(new File(kernelPath));
        System.out.println(makeParam);
    }
}
